/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.mochaccino.framework.scheduler.store;

import com.google.code.mochaccino.framework.scheduler.datasource.ConnectionProviderNamed;
import com.google.common.base.Preconditions;
import java.util.concurrent.TimeUnit;
import javax.inject.Provider;
import org.quartz.impl.DefaultThreadExecutor;
import org.quartz.impl.jdbcjobstore.DriverDelegate;
import org.quartz.impl.jdbcjobstore.InvalidConfigurationException;
import org.quartz.impl.jdbcjobstore.JobStoreSupport;
import org.quartz.impl.jdbcjobstore.Semaphore;
import org.quartz.impl.jdbcjobstore.SimpleSemaphore;

/** JDBC Job Store Provider */
public abstract class JobStoreProvider<T extends JobStoreSupport> implements Provider<T> {
	private Class<? extends DriverDelegate> driverDelegate                             = org.quartz.impl.jdbcjobstore.StdJDBCDelegate.class;
	private ConnectionProviderNamed         connectionProvider                         = null;
	private DefaultThreadExecutor           threadExecutor                             = new DefaultThreadExecutor();
	private Semaphore                       lockHandler                                = new SimpleSemaphore();
	private String                          delegateInitString                         = null;
	private String                          selectWithLockSQL                          = null;
	private String                          tablePrefix                                = "QRTZ_";
	private boolean                         acquireTriggersWithinLock                  = true;
	private boolean                         clustered                                  = false;
	private boolean                         dontSetAutoCommitFalse                     = false;
	private boolean                         doubleCheckLockMisfireHandler              = true;
	private boolean                         lockOnInsert                               = true;
	private boolean                         makeThreadsDaemons                         = false;
	private boolean                         threadsInheritInitializersClassLoadContext = false;
	private boolean                         txIsolationLevelSerializable               = false;
	private boolean                         useDBLocks                                 = true;
	private boolean                         useProperties                              = Boolean.TRUE;
	private int                             maxToRecoverAtATime                        = 1;
	private int                             threadPoolSize                             = 10;
	private long                            clusterCheckInterval                       = TimeUnit.SECONDS.toMillis( 1L );
	private long                            dbRetryInterval                            = TimeUnit.SECONDS.toMillis( 1L );
	private long                             misfireThreshold                           = TimeUnit.SECONDS.toMillis( 30L );

	@Override
	public T get() {
		try {
			T bean = create();
			bean.setDataSource( connectionProvider.getName() );
			bean.setDontSetAutoCommitFalse( dontSetAutoCommitFalse );
			bean.setAcquireTriggersWithinLock( acquireTriggersWithinLock );
			bean.setClusterCheckinInterval( clusterCheckInterval  );
			bean.setDbRetryInterval( dbRetryInterval  );
			bean.setDoubleCheckLockMisfireHandler( doubleCheckLockMisfireHandler );
			bean.setDriverDelegateClass( driverDelegate.getName() );
			bean.setDriverDelegateInitString( delegateInitString );
			bean.setIsClustered( clustered );
			bean.setLockHandler( lockHandler );
			bean.setLockOnInsert( lockOnInsert );
			bean.setMakeThreadsDaemons( makeThreadsDaemons );
			bean.setMaxMisfiresToHandleAtATime( maxToRecoverAtATime );
			bean.setMisfireThreshold( misfireThreshold  );
			bean.setTablePrefix( tablePrefix );
			bean.setThreadExecutor( threadExecutor );
			bean.setThreadPoolSize( threadPoolSize );
			bean.setThreadsInheritInitializersClassLoadContext( threadsInheritInitializersClassLoadContext );
			bean.setTxIsolationLevelSerializable( txIsolationLevelSerializable );
			bean.setUseDBLocks( useDBLocks );
			bean.setUseProperties( Boolean.toString( useProperties ) );
			if ( selectWithLockSQL != null ) {
				bean.setSelectWithLockSQL( selectWithLockSQL );
			}
			configure( bean );
			return bean;
		} catch ( InvalidConfigurationException e ) {
			throw new RuntimeException( e.getMessage(), e );
		}
	}

	protected abstract void configure( T bean ) throws InvalidConfigurationException;

	protected abstract T create();

	public void setAcquireTriggersWithinLock( boolean acquireTriggersWithinLock ) {
		this.acquireTriggersWithinLock = acquireTriggersWithinLock;
	}

	public void setClusterCheckInterval( long clusterCheckInterval, TimeUnit unit ) {
		this.clusterCheckInterval = Preconditions.checkNotNull( unit ).toMillis(clusterCheckInterval);
	}

	public void setClustered( boolean clustered ) {
		this.clustered = clustered;
	}

	public void setConnectionProvider( ConnectionProviderNamed connectionProvider ) {
		this.connectionProvider = Preconditions.checkNotNull( connectionProvider );
	}

	public void setDbRetryInterval( long dbRetryInterval, TimeUnit unit ) {
		this.dbRetryInterval = Preconditions.checkNotNull( unit ).toMillis( dbRetryInterval);
	}

	public void setDelegateInitString( String delegateInitString ) {
		this.delegateInitString = delegateInitString;
	}

	public void setDontSetAutoCommitFalse( boolean dontSetAutoCommitFalse ) {
		this.dontSetAutoCommitFalse = dontSetAutoCommitFalse;
	}

	public void setDoubleCheckLockMisfireHandler( boolean doubleCheckLockMisfireHandler ) {
		this.doubleCheckLockMisfireHandler = doubleCheckLockMisfireHandler;
	}

	public void setDriverDelegate( Class<? extends DriverDelegate> driverDelegate ) {
		this.driverDelegate = driverDelegate;
	}

	public void setLockHandler( Semaphore lockHandler ) {
		this.lockHandler = Preconditions.checkNotNull( lockHandler );
	}

	public void setLockOnInsert( boolean lockOnInsert ) {
		this.lockOnInsert = lockOnInsert;
	}

	public void setMakeThreadsDaemons( boolean makeThreadsDaemons ) {
		this.makeThreadsDaemons = makeThreadsDaemons;
	}

	public void setMaxToRecoverAtATime( int maxToRecoverAtATime ) {
		this.maxToRecoverAtATime = maxToRecoverAtATime;
	}

	public void setMisfireThreshold( int misfireThreshold, TimeUnit unit ) {
		this.misfireThreshold = Preconditions.checkNotNull( unit ).toMillis( misfireThreshold );
	}

	public void setSelectWithLockSQL( String selectWithLockSQL ) {
		this.selectWithLockSQL = Preconditions.checkNotNull( selectWithLockSQL );
	}

	public void setTablePrefix( String tablePrefix ) {
		this.tablePrefix = Preconditions.checkNotNull( tablePrefix );
	}

	public void setThreadExecutor( DefaultThreadExecutor threadExecutor ) {
		this.threadExecutor = Preconditions.checkNotNull( threadExecutor );
	}

	public void setThreadPoolSize( int threadPoolSize ) {
		Preconditions.checkState( threadPoolSize > 0 );
		this.threadPoolSize = threadPoolSize;
	}

	public void setThreadsInheritInitializersClassLoadContext( boolean threadsInheritInitializersClassLoadContext ) {
		this.threadsInheritInitializersClassLoadContext = threadsInheritInitializersClassLoadContext;
	}

	public void setTxIsolationLevelSerializable( boolean txIsolationLevelSerializable ) {
		this.txIsolationLevelSerializable = txIsolationLevelSerializable;
	}

	public void setUseDBLocks( boolean useDBLocks ) {
		this.useDBLocks = useDBLocks;
	}

	public void setUseProperties( boolean useProperties ) {
		this.useProperties = useProperties;
	}
}
